package fr.grassaud.hmvc.info;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.grassaud.hmvc.component.Controller;
import fr.grassaud.hmvc.component.Model;
import fr.grassaud.hmvc.component.ModelExtended;
import fr.grassaud.hmvc.component.View;
import fr.grassaud.hmvc.component.ViewExtended;
import fr.grassaud.hmvc.event.OnControllerEvent;
import fr.grassaud.hmvc.event.OnModelEvent;
import fr.grassaud.hmvc.event.OnViewEvent;
import fr.grassaud.hmvc.util.HMVCIntrospector;

/**
 * Class to retrive some meta data about the a triad
 * 
 * @author vgrassaud
 * 
 */
public class TriadInfo {

    /**
     * The class of the controller of the triad
     */
    private Class<?> controllerClass;

    private Class<?> viewExtendedClass;

    private Class<?> modelExtendedClass;

    /**
     * The children controller classes of the triad
     */
    private List<Field> childrenClass;

    /**
     * The method used to initialize the triad
     */
    private Method controllerInitMethod;

    private Map<String, Method> modelMethods;

    private Map<String, Method> viewMethods;

    private Map<String, Method> controllerMethods;

    private List<ViewInfo> viewInfos;

    private ViewInfo declaringViewInfos;

    private List<ModelInfo> modelInfos;

    private ModelInfo declaringModelInfos;

    private Logger logger = LoggerFactory.getLogger(TriadInfo.class);
    
    private Method releaseMethod;
    
    

    /**
     * Inspect info about the triad from the given controller class
     * 
     * @param controllerClass
     *            the controllqer class to inspect
     */
    public TriadInfo(Class<?> controllerClass) {
	this.controllerClass = controllerClass;
	this.modelInfos = new ArrayList<ModelInfo>();
	this.viewInfos = new ArrayList<ViewInfo>();
	readInfo();

    }

    /**
     * Read the controller class to set the meta data
     */
    private void readInfo() {
	// assure that the controller class is well a controller component
	if (!HMVCIntrospector.isControllerComponent(controllerClass)) {
	    logger.error("The controller class {} should be anotated with {}", controllerClass, Controller.class);
	    throw new IllegalArgumentException("the controller should be annotated with" + Controller.class.getName());
	}
	// children of this controller
	childrenClass = new ArrayList<Field>();
	// methods to invoke from model
	modelMethods = new HashMap<String, Method>();
	// methods to invoke from a view
	viewMethods = new HashMap<String, Method>();
	// methods to invoke from a controller
	controllerMethods = new HashMap<String, Method>();

	// check is this controller extend a controller class and the view is
	// also extended
	// this is usefull to declare simply the view as well class for the
	// developper and not using
	// the super class
	if (controllerClass.isAnnotationPresent(ViewExtended.class)) {
	    ViewExtended annotation = controllerClass.getAnnotation(ViewExtended.class);
	    viewExtendedClass = annotation.value();
	}

	// check is this controller extend a controller class and the model is
	// also extended
	// this is usefull to declare simply the view as well class for the
	// developper and not using
	// the super class
	if (controllerClass.isAnnotationPresent(ModelExtended.class)) {
	    ModelExtended annotation = controllerClass.getAnnotation(ModelExtended.class);
	    modelExtendedClass = annotation.value();
	}

	// reads all fields of the controller class even the fields of the super
	// class
	List<Field> fields = HMVCIntrospector.getAllFields(controllerClass);
	for (Field f : fields) {
	    // add a child controller
	    if (HMVCIntrospector.isChild(f)) {
		childrenClass.add(f);
	    }
	    // add the view
	    if (HMVCIntrospector.isView(f)) {
		ViewInfo vInfo = new ViewInfo(f);
		if (f.getDeclaringClass().equals(controllerClass)) {
		    this.declaringViewInfos = vInfo;
		}
		this.viewInfos.add(vInfo);
		checkViewDefinition(vInfo);
	    }
	    // add the model
	    if (HMVCIntrospector.isModel(f)) {
		ModelInfo modelInfo = new ModelInfo(f);
		if (f.getDeclaringClass().equals(controllerClass)) {
		    this.declaringModelInfos = modelInfo;
		}
		modelInfos.add(modelInfo);
		checkModelDefinition(modelInfo);
	    }
	}

	checkControllerDefinition();

	for (Method m : HMVCIntrospector.getAllMethods(controllerClass)) {
	    if (HMVCIntrospector.isInitMethod(m)) {
		controllerInitMethod = m;
	    }
	    if (HMVCIntrospector.isReleaseMethod(m)) {
		releaseMethod = m;
	    }
	    if (m.isAnnotationPresent(OnModelEvent.class)) {
		OnModelEvent annotation = m.getAnnotation(OnModelEvent.class);
		modelMethods.put(annotation.name(), m);
	    }
	    if (m.isAnnotationPresent(OnViewEvent.class)) {
		OnViewEvent annotation = m.getAnnotation(OnViewEvent.class);
		viewMethods.put(annotation.name(), m);
	    }

	    if (m.isAnnotationPresent(OnControllerEvent.class)) {
		OnControllerEvent annotation = m.getAnnotation(OnControllerEvent.class);
		controllerMethods.put(annotation.name(), m);
	    }
	}

    }

    /**
     * Checks the definition of the controller
     */
    private void checkControllerDefinition() {
	if (this.viewInfos.isEmpty()) {
	    logger.warn("Warning, there is no view declared for the controller class {} ", controllerClass);
	    throw new IllegalStateException("there is no view declared for the controller class " + controllerClass);

	}

	if (declaringViewInfos == null) {
	    declaringViewInfos = viewInfos.get(0);
	}

	//model can be undeclared
	if (this.modelInfos.isEmpty()) {
	    logger.warn("Warning, there is no model declared for the controller class {} ", controllerClass);

	} else {
	    if (declaringModelInfos == null) {
		declaringModelInfos = modelInfos.get(0);
	    }
	}

    }

    /**
     * Returns the method annotated by the <code>OnViewEvent</code> and the given event name
     * @param eventName the name of the view event
     * @return the method of this controller, or <code>null</code>, if none
     */
    public Method getViewEventMethod(String eventName) {
	return viewMethods.get(eventName);
    }

    /**
     * Returns the method annotated by the <code>OnModelEvent</code> and the given event name
     * @param eventName the name of the model event
     * @return the method of this controller, or <code>null</code>, if none
     */
    public Method getModelEventMethod(String eventName) {
	return modelMethods.get(eventName);
    }

    /**
     * Returns the method annotated by the <code>OnControllerEvent</code> and the given event name
     * @param eventName the name of the controller event
     * @return the method of this controller, or <code>null</code>, if none
     */
    public Method getControllerEventMethod(String eventName) {
	return controllerMethods.get(eventName);
    }

    /**
     * Returns the view class managed by this controller. Note that if this controller use the 
     * <code>ViewExtended</code> annotation, this method will return the class defined by the annotation
     * @return the view class managed by this controller
     */
    public Class<?> getViewClass() {
	Class<?> result = declaringViewInfos.getViewClass();
	if (viewExtendedClass != null) {
	    result = viewExtendedClass;
	}
	return result;
    }

    /**
     * Returns the model class managed by this controller. Note that if this controller use the 
     * <code>ModelExtended</code> annotation, this method will return the class defined by the annotation
     * @return the view class managed by this controller, or <code>null</code> if there is no model used by this controller
     */
    public Class<?> getModelClass() {
	Class<?> result = null;
	if ( declaringModelInfos != null  ) {
 	   result = declaringModelInfos.getModelClass();
	}
	if (modelExtendedClass != null) {
	    result = modelExtendedClass;
	}
	return result;
    }

    /**
     * Returns the controller class 
     * @return  the controller class, cannot be <code>null</code>
     */
    public Class<?> getControllerClass() {
	return controllerClass;
    }

    /**
     * Returns all fields annotated by the <code>Child</code> annotation
     * @return all fields annotated by the <code>Child</code> annotation
     */
    public List<Field> getChildrenClass() {
	return childrenClass;
    }

    /**
     * Returns the method used to initialize this controller, it means the method annotated by the <code>OnInit</code>
     * annotation.
     * @return  the method used to initialize this controller, it means the method annotated by the <code>OnInit</code>
     * annotation.
     */
    public Method getControllerInitMethod() {
	return this.controllerInitMethod;
    }

    /**
     * Returns the method used to initialize the view, it means the method annotated by the <code>OnInit</code>
     * annotation.
     * @return  the method used to initialize the view, it means the method annotated by the <code>OnInit</code>
     * annotation.
     */
    public Method getViewInitMethod() {
	return declaringViewInfos.getInitMethod();
    }

    
    /**
     * Returns the method used to initialize the model, it means the method annotated by the <code>OnInit</code>
     * annotation.
     * @return  the method used to initialize the model, it means the method annotated by the <code>OnInit</code>
     * annotation.
     */
    public Method getModelInitMethod() {
	Method result = null;
	if ( declaringModelInfos != null) {
	 result = declaringModelInfos.getInitMethod();
	}
	return result;
    }

    
    /**
     * Checks the declaration of the view in this triad
     * @param vInfo the view info to check 
     */
    private void checkViewDefinition(ViewInfo vInfo) {
	View annotation = vInfo.getViewClass().getAnnotation(View.class);
	if (!annotation.controlledBy().isAssignableFrom(controllerClass)) {
	    throw new IllegalStateException("the view should be controlled by a controller of type " + controllerClass);
	}

    }

    
    /**
     * Checks the declaration of the model in this triad
     * @param mInfo the model info to check 
     */
    private void checkModelDefinition(ModelInfo mInfo) {
	Model annotation = mInfo.getModelClass().getAnnotation(Model.class);
	if (!annotation.controlledBy().isAssignableFrom(controllerClass)) {
	    throw new IllegalStateException("the model should be controlled by a controller of type " + controllerClass);
	}
    }

    /**
     * Returns the name of the model field
     * @return the name of the model field, can return <code>null</code> it there is no model declared
     */
    public String getModelFieldName() {
	String result = null;
	if ( declaringModelInfos != null ) {
	    result = declaringModelInfos.getName();
	}
	return result;
    }

    
    /**
     * Returns the name of the view field
     * @return the name of the view field
     */
    public String getViewFieldName() {
	return declaringViewInfos.getName();
    }

    /**
     * Returns all model fields 
     * @return all model fields, can be empty
     */
    public List<Field> getModelFields() {
	List<Field> result = new ArrayList<Field>();
	for (ModelInfo mi : modelInfos) {
	    result.add(mi.getField());
	}
	return result;
    }

    
    /**
     * Returns all view fields 
     * @return all view fields, cannot be empty, at least one 
     */
    public List<Field> getViewFields() {
	List<Field> result = new ArrayList<Field>();
	for (ViewInfo mi : viewInfos) {
	    result.add(mi.getField());
	}
	return result;
    }
    
    
    public Method getReleaseMethod() {
	return this.releaseMethod;
    }

    
    public List<ViewInfo> getAllViewInfo() {
	return this.viewInfos;
    }
    
    public List<ModelInfo> getAllModelInfo() {
	return this.modelInfos;
    }
}
